/* ctsw.c : context switcher
 */

#include <kernel.h>

/* This is as good a place as any to write the kernel state recovery storage declaration */
static void* kernelStackPointer;
void syscall_entry_point();
void timer_entry_point();
void common_entry_point();

void contextInit(){
//	__asm__("jmp syscall_entry_point\n");
	set_evec(67, syscall_entry_point);
	set_evec(32, timer_entry_point);
	asm volatile(
		"cli\n"); /* Disable interupts during kernel mode exec */
	if(DO_PREEMPTION)
		initPIT(PREEMPTION_DIVISOR);
}

/*	Contextswitch from the point of view of the dispatcher is responsible for running a process and returning
	a system call enum code. From the point of view of the process it is the re-entry point from a system call. */
extern system_call contextswitch( pcb* process ){
	system_call call;

	if (DISP_UT_FAKE_PROCESSES){
		static int tester_cycle = 0;
		/* This satisfies the linker, and isn't used */
		asm volatile( 
			"syscall_entry_point:\n" \
			"timer_entry_point:\n" \
			"common_entry_point:\n");
			
		/* The dispatcher unit test does not ever actually leave kernel space or run any processes. 
			It simply spawns 'fake' processes which pretend to call system calls to test how the queues, 
			memory management, and process management systems work. */
		kprintf("\n\n\nTest Cycle %d.\n", tester_cycle);
		switch (tester_cycle){
			case 0: 
				if(rand()%2) {
					create(NULL, 1024, "child"); /* Pretend to spawn a child */
					call = SYSYIELD; 
					break;
				}
			case 1:
			case 2:
			case 3:
			case 4:
				call = SYSYIELD;
				break;
			case 5:
				if(rand()%2) {
					call = SYSSTOP;
					break;
				}
			default:
				call = SYSYIELD;
				break;
		}
		tester_cycle = (tester_cycle + 1) % 6;
		return call; /* End of dispatcher unit test */
	}
	
	/* Start of real context switcher code */ 
	static unsigned int was_ext_interrupt;
	static unsigned int returnCode;
	static unsigned int ESP;
	ESP = (unsigned int) process->stackPointer; 
	
	/* Need to restore the EAX that the process had when the int went off. 
	   If it was a syscall, the dispatcher would have written this value to be the return code. */
	returnCode = process->procEAX; /* Value was preserved in the PCB */
	
	if (DISP_CYCLE_DELAY){
		/* My kernel slowing code for debugging times out the PIT, which means the int is already going off when we return.
		   Thus the proc never gets time. Reset the PIC. */
		initPIT(PREEMPTION_DIVISOR);
	}

	verbose("Context switching to process %d.\n", process->pid);
	asm volatile( 
		"\tpushf\n\t"				/* Kregs save */ \
		"pusha\n\t"					/* Kflgs save */ \
		"movl %6, %%eax\n\t"		/* Memory returnCode value to CPU */\
		"movl %%esp, %1\n\t" 		/* K SP save */	\
		"movl %4, %%esp\n\t" 		/* P SP load */	\
		"movl %%eax, 28(%%esp)\n\t"	/* CPU returnCode value to P stk */\
		"popa\n\t" 					/* P regs load to cpu */	\
		"iret\n" 					/* P flags load to cpu */	\
	"timer_entry_point:\n\t" \
		"cli\n\t" 					/* Disable interrupts */ \
		"pusha\n\t" 				/* Pregs save */\
		"movl $1, %%ecx\n\t" 		/* Store was_ext_interrupt in ecx */\
		"jmp common_entry_point\n\t" \
	"syscall_entry_point:\n\t" \
		"cli\n\t" 					/* Disable interrupts */ \
		"pusha\n\t" 				/* Pregs save */\
		"movl $0, %%ecx\n\t" 		/* Store !was_ext_interrupt in ecx */\
	"common_entry_point:\n\t"	\
		"movl %%esp, %0\n\t"		/* P SP save */\
		"movl %5, %%esp\n\t"		/* K SP load */\
		"movl %%eax, 28(%%esp)\n\t"	/* Current returnCode value to K stk */\
		"movl %%ecx, 24(%%esp)\n\t"	/* Current was_ext_interrupt val to K stk */\
		"popa\n\t"					/* Kstk regs to cpu */\
		"popf\n"					/* Kstk flags to cpu */\
		"movl %%eax, %2\n\t"		/* CPU returnCode value to memory */\
		"movl %%ecx, %3\n\t"		/* CPU was_ext_interrupt val to mem */\
		:"=m" (ESP),"=m" (kernelStackPointer),"=m" (returnCode), "=m" (was_ext_interrupt)	\
		:"m" (ESP),"m" (kernelStackPointer), "m" (returnCode) \
		:"%eax", "%ecx"); /* clobbered by the kernel in processing the system call. */
		
	verbose("Returned to kernel.\n", process->pid);
	
	process->stackPointer = (void*)ESP;

	if (was_ext_interrupt){
		/* The context switcher was called because of a hardware interupt. 
			Save the process's EAX value, now saved in returnCode, in the PCB for that process. */
		if (SYSCALL_UNIT_TEST) kprintf("Interrupt occured! eax (returnCode) = 0x%X.\n", returnCode);
		process->procEAX = returnCode;
		return TIMER_INT;
	}
		
	/* What is the system call type? Get it from the process' saved cpu context which was pushed with pusha. 
		Call enum type is in 'eax'. pcb->stackpointer is currently pointing to 'edi' in the stack. */	
	call = *(process->stackPointer + 7 * sizeof(uint32)); /* process PCB saved eax. Used to determine the syscall type, or simply returned later if there was a hardware interrupt. */
	return call;
}






